/*
	freezer -- contains methods that convert a file type into a file_data type, ready to be passed to userspace for freezing
*/

#include <linux/slab.h> 
#include <linux/stat.h>
#include <linux/fcntl.h>
#include <linux/file.h>
#include <linux/uio.h>
#include <linux/smp_lock.h>
#include <linux/fsnotify.h>
#include <linux/security.h>
#include <linux/module.h>
#include <linux/syscalls.h>
#include <linux/pagemap.h>
#include <linux/list.h>
#include <asm/uaccess.h>
#include <asm/unistd.h>
#include <linux/freezer.h>
#include <asm/semaphore.h>
#include <linux/wait.h>  

#define PROTECTED "freeze-safe."
#define LOG_FILE PROTECTED "logFile.txt"

DECLARE_MUTEX(mut);
DECLARE_MUTEX(waker);
DECLARE_WAIT_QUEUE_HEAD(waiter);

volatile int WAIT = 1;
int HAS_DATA = 0;
int INIT_FREEZE = 0;

struct file_data {
  unsigned int unique_id;
  int newFile;
  int regFile;
  char path_name[128];
};

struct file_data *transfer;

void wake_up_freezer() {
  WAIT = 0;
  wake_up(&waiter);
}

int freezer_hasData() {
  return HAS_DATA;
}

//Return struct to pass to user application
struct file_data * get_frozen_file() {
  HAS_DATA = 0;  
  return transfer;
}

void begin_freeze() {
  if (INIT_FREEZE == 0) {
    init_waitqueue_head(&waiter);
    INIT_FREEZE = 1;
    WAIT = 1;
  }
  else {
    wake_up_all(&waiter);
    INIT_FREEZE = 0;
  }
}

/* interceptor(*__file, nFile)
   	called when user application modifies/creates file

   	input: file to modify by kernel, newFile?
		output: 1 -> "OK"

		out var: (global) transfer, set with attributes of __file
*/
int interceptor(struct file *__file, int nFile) {
	//Thawed? Don't backup file
	//	(most likely called during bootup)
  if (INIT_FREEZE == 0) return 1;

	//Is it file (not device, socket, directory)?
  if (S_ISREG(__file->f_dentry->d_inode->i_mode)) {
    //Set (global) transfer with extracted attributes from __file
    //also check to see if this file was already frozen
    down(&mut);
    acquire_path(__file);
    transfer->newFile = nFile;
    transfer->regFile = 1;
    transfer->unique_id = __file->f_dentry->d_name.hash;
    up(&mut);
    if (is_protected_file(transfer->path_name) == NULL) {
      down(&waker);
      HAS_DATA = 1;      
      wait_event(waiter, (WAIT == 0));
      WAIT = 1;
      kfree(transfer);
      up(&waker);
    }
  }
	
  return 1;
}

/* acquire_path(__file)	
	input: a file (not FILE)		
	out var: (global) transfer
*/
void acquire_path(struct file *__file) {
  struct dentry *temp;

  char temp_name[128];

  //Make new file_data struct to pass to user
  transfer = (struct file_data *)kmalloc(sizeof(struct file_data), __GFP_NORETRY);

  temp = __file->f_dentry;

  //Copy filename for output
  strcpy(transfer->path_name, temp->d_iname);
  temp = temp->d_parent;

  //Follow i_node parents back to '/'
  while (strcmp(temp->d_iname, "/")) {
    strcpy(temp_name, temp->d_iname);
    strcat(temp_name, "/");
    strcat(temp_name, transfer->path_name);
    strcpy(transfer->path_name, temp_name);
    temp = temp->d_parent;
  }
}

char * is_protected_file(char * buffer) {
  return strstr(buffer, PROTECTED);
}
